package com.msh.starter.id.generate.instance;

import com.msh.frame.common.util.DateUtil;
import com.msh.frame.interfaces.IIdGenerateServiceId;
import com.msh.frame.interfaces.IdGenerateable;
import lombok.extern.slf4j.Slf4j;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeUnit;


/**
 * LONG类型正数最大个数19个，最大正整数9223372036854775807 生成带有年月日开头的id 日期6位,当天秒数5位，服务器id 1位 自增数字7位
 * 进过测试(100个线程生成5000万个id，时间所需1757毫秒) ---------------------------------------------------------------
 * 可生成多个对象，每个对象生成的id唯一 但多个对象生成的id可能重复, 生成id最后的自增数字 位数数据中有自增效果， 可在分库分表时，取模可以平均分配数据 给需要分库分表的id
 * 使用独立的id生成器对象 保证id取余后的连续性 -----------------------------------------------------------------
 * 如果有可能出现几个小时都没有新数据的情况 启动updateLastPassDaySecondThread线程定时更新最后一次经过秒数
 */
@Slf4j
public class DateIdGenerate implements IdGenerateable {
  private static final Object LOCK_OBJECT = new Object();
  /**
   * 一天秒数共占多少个数字位 一天 86400 秒 共占5位
   */
  private static final int ONE_DAY_NUMBER_MULTIPLE = 100000;
  /**
   * 服务器id所占数字位数
   */
  private final int serviceIdNumberMultiple;

  /**
   * 每秒生成id 位数
   */
  private final int indexNumberMultiple;

  /**
   * 服务器id位移后的值
   */
  private static volatile long serviceIdBitShiftValue;

  /**
   * 最后一次index为0时的当天经过秒数
   */
  private volatile long lastPassDaySecond = 0L;

  /**
   * 每次生成一个id自增1，当大于iMax时置0， 保证lastIndex几个数据位一直处于自增状态 可在分库，分表时独立生成该对象来获取id
   */
  private volatile int lastIndex = 0;

  /**
   * 计数累计
   */
  private volatile int i = 0;

  /**
   * 计数累计 最大值
   */
  private final int iMax;

  /**
   * 日期开头的long值 后面有dateBackNumberCount位数字
   */
  private volatile long dateFront;


  /**
   * 两个参数相乘不能大于100000000
   *
   * @param indexNumberMultiple 每秒生成id 位数 10的倍数
   */
  public DateIdGenerate(IIdGenerateServiceId idGenerateServiceId, int indexNumberMultiple) {
    if(indexNumberMultiple < 1 || (indexNumberMultiple > 1 && indexNumberMultiple<10) || (indexNumberMultiple >= 10 && indexNumberMultiple % (indexNumberMultiple/10) != 0)){
      log.error("indexNumberMultiple必须是1或10的倍数，请重新配置. indexNumberMultiple: {}", indexNumberMultiple);
      System.exit(1);
    }
    this.serviceIdNumberMultiple = idGenerateServiceId.digit();
    // 两个参数相乘不能大于100000000, 否则会超出长度范围, 判断为了防止越界
    if(serviceIdNumberMultiple > 100000000 || indexNumberMultiple> 100000000 || (serviceIdNumberMultiple * indexNumberMultiple > 100000000)){
      log.error("生成id长度超出范围，请重新配置. digit:{}, indexNumberMultiple: {}", idGenerateServiceId.digit(), indexNumberMultiple);
      System.exit(1);
    }
    this.indexNumberMultiple = indexNumberMultiple;
    iMax = indexNumberMultiple - 1;
    Long dayLong = calculateDateFront();
    if (null == dayLong) {
      throw new RuntimeException("生成日期头出错");
    }
    dateFront = dayLong;
    serviceIdBitShiftValue = idGenerateServiceId.serverId() * (long)indexNumberMultiple;
    updateLastPassDaySecondThread();
  }

  /**
   * 生成大概15位数
   */
  public DateIdGenerate() {
    this(new OneServiceIdGenerateServiceId(), 1000);
  }

  @Override
  public Long getUniqueId() {
    synchronized (LOCK_OBJECT) {
      long now = DateUtil.getPassDayMilliSecond() / 1000;
      if (now == lastPassDaySecond) {
        i++;
        //超出每秒最大生成量时，等待过一秒后继续
        while (i > iMax) {
          now = DateUtil.getPassDayMilliSecond() / 1000;
          if (now != lastPassDaySecond) {
            calculateNowLastPassDaySecond(now);
            break;
          }
          try {
            TimeUnit.MILLISECONDS.sleep(1);
          } catch (InterruptedException e) {
          }
        }
      } else {
        calculateNowLastPassDaySecond(now);
      }

      long destID = dateFront + lastPassDaySecond * serviceIdNumberMultiple * indexNumberMultiple
          + serviceIdBitShiftValue + lastIndex;
      lastIndex++;
      if (lastIndex > iMax) {
        lastIndex = 0;
      }
      return destID;
    }
  }

  private void calculateNowLastPassDaySecond(long now) {
    if (now < lastPassDaySecond) {
      Long dayLong = calculateDateFront();
      if (null != dayLong
          && dayLong != dateFront) {
        dateFront = dayLong;
      }
      dateFront = dayLong;
      lastPassDaySecond = now;
      i = 0;
    } else if (now > lastPassDaySecond) {
      lastPassDaySecond = now;
      i = 0;
    }

  }

  private Long calculateDateFront() {
    Long dayLong = null;
    try {
      DateFormat df = new SimpleDateFormat("yyMMdd");
      String day = df.format(new Date());
      dayLong = Long.valueOf(day) * ONE_DAY_NUMBER_MULTIPLE * serviceIdNumberMultiple
          * indexNumberMultiple;
    } catch (Exception e) {

    }
    return dayLong;
  }


  /**
   * 启动线程 更新最后一次当天经过秒数线程 如果一直没有更新，上一次的当天经过时间与这次当天经过时间间隔到一定范围，会出现日期头不更新 例: 第 M 次生成id 190512
   * lastPassDaySecond 3600 190512日期在经过了3600秒后生成一次id 第 M+1 次生成id 190513 在超过4000秒
   * 190513日期在经过了4000秒后生成一次id 生成id 时, now > lastPassDaySecond ,不会更新日期头  则生成的id还会以190512开头
   */
  private void updateLastPassDaySecondThread() {
    Thread thread = new Thread(() -> {
      while (true) {
        try {
          TimeUnit.HOURS.sleep(1);
          synchronized (LOCK_OBJECT) {
            Long dayLong = calculateDateFront();
            if (null != dayLong
                && dayLong != dateFront) {
              dateFront = dayLong;
              lastPassDaySecond = -1;
              i = 0;
            }
          }
        } catch (Exception e) {
        }
      }
    });
    thread.setDaemon(true);
    thread.start();
  }
  public static void main(String[] args) {
    for(int i=1; i<10; i++){
      System.out.println("i: " +i +".  " + new DateIdGenerate((IIdGenerateServiceId) new IIdGenerateServiceId() {
        @Override
        public int serverId() {
          return 1;
        }

        @Override
        public int digit() {
          return 10;
        }

        @Override
        public int bit() {
          return 3;
        }
      }, (int) Math.pow(10, i)).getUniqueId());
    }
  }
}
